/***********************************************
 This file is part of redia-lib-utils-ios
 
 
 Copyright (c) 2015 Redia A/S
 
 Redia-library-app-ios is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 Redia-library-app-ios is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with redia-library-app-ios.  If not, see <http://www.gnu.org/licenses/>.
 
 *********************************************** */

#import "JSONRPCClient.h"
#import "JSONRPCRequestHelper.h"
#import "JSONRPCCustomHTTPHeader.h"
#import "JSONRPCError.h"

static NSMutableArray *_outstandingNSURLConnections;

@interface JSONRPCClient ()

@property (strong, nonatomic) NSMutableArray *dispatchQueue;
@property (strong, nonatomic) NSMutableArray *customHttpHeaders;
@property (strong, nonatomic) NSMutableArray *myRequests;

@end

@implementation JSONRPCClient

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        if (_outstandingNSURLConnections == nil) {
            _outstandingNSURLConnections = [NSMutableArray new];
        }
        _myRequests = [NSMutableArray new];
    }
    return self;
}

+ (JSONRPCClient *)JSONRPCClientWithServer:(NSURL *)serverURL delegate:(id<JSONRPCClientDelegate>)delegate
{
    JSONRPCClient *client = [[JSONRPCClient alloc] init];
    
    if (client) {
        client.serverURL = serverURL;
        client.delegate = delegate;
    }
    
    return client;
}

+ (JSONRPCClient*)JSONRPCClientWithServer:(NSURL*)serverURL block:(CompletionHandlerBlock)block
{
    JSONRPCClient *client = [[JSONRPCClient alloc] init];
    
    if (client) {
        client.serverURL = serverURL;
        client.completionHandlerBlock = block;
    }
    
    return client;
}

#pragma One-shot block based calls

+ (void)performMethod:(NSString *)method
           withParams:(NSArray *)params
             onServer:(NSURL *)server
         onCompletion:(CompletionHandlerBlock)block
{
    JSONRPCClient *client = [JSONRPCClient JSONRPCClientWithServer:server block:block];
    [client performMethod:method args:params requestId:0];
}

- (void)performMethod:(NSString *)method
           withParams:(NSArray *)params
         onCompletion:(CompletionHandlerBlock)block
{
    self.CompletionHandlerBlock = block;
    [self performMethod:method args:params requestId:0];
}

#pragma mark - Stopping running requests

+ (void)stopAllRequests
{
    for (NSURLConnection *connection in _outstandingNSURLConnections) {
        [connection cancel];
    }
    _outstandingNSURLConnections = [NSMutableArray new];
}

+ (void)removeConnection:(NSURLConnection *)connection
{
    [_outstandingNSURLConnections removeObject:connection];
}

- (void)removeConnection:(NSURLConnection*)connection
{
    [self.myRequests removeObject:connection];
}

- (void)stopRequests
{
    for (NSURLConnection *connection in self.myRequests) {
        [connection cancel];
    }
    self.myRequests = [NSMutableArray new];
}


#pragma mark - Custom HTTP headers

- (void)addCustomHttpHeader:(NSString*)key value:(NSString*)value
{
    if (self.customHttpHeaders == nil) {
        self.customHttpHeaders = [NSMutableArray array];
    }
    
    [self.customHttpHeaders addObject:[[JSONRPCCustomHTTPHeader alloc] initWithKey:key value:value]];
}

- (BOOL)removeCustomHttpHeader:(NSString*)key
{
    if (self.customHttpHeaders) {
        BOOL foundIt = NO;
        for (JSONRPCCustomHTTPHeader *header in self.customHttpHeaders) {
            if ([header.key caseInsensitiveCompare:key] == NSOrderedSame) {
                [self.customHttpHeaders removeObject:header];
                foundIt = YES;
                break;
            }
        }
        return foundIt;
    }
    else {
        return NO;
    }
}

- (void)removeAllCustomHttpHeaders
{
    self.customHttpHeaders = nil;
}


#pragma mark - Sending a single request

- (BOOL)sanityCheck
{
    // Sanity check - do we have a server URL and a delegate?
    if (self.serverURL == nil) {
        NSLog(@"Error: Attempt to perform JSONRPC method without setting a server URL.");
        return NO;
    }
    
    if (self.delegate == nil && self.completionHandlerBlock == nil) {
        NSLog(@"Error: Attempt to perform JSONRPC method without setting a delegate.");
        return NO;
    }
    return YES;
}

- (NSData*)buildRequest:(NSString *)method args:(id)args requestId:(NSInteger)requestId
{
    // Create a JSONRPC request.
    NSMutableDictionary *requestObject = [NSMutableDictionary dictionary];
    [requestObject setObject:@"2.0" forKey:@"jsonrpc"];
    [requestObject setObject:method forKey:@"method"];
    [requestObject setObject:args forKey:@"params"];
    [requestObject setObject:[NSNumber numberWithInteger:requestId] forKey:@"id"];
    
    // Try to convert the request to JSON.
    NSError *error;
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestObject
                                                          options:NSJSONWritingPrettyPrinted
                                                            error:&error];
    if (requestData) {
        if (self.delegate && [self.delegate respondsToSelector:@selector(JSONRPCClient:requestDataGenerated:)]) {
            [self.delegate JSONRPCClient:self requestDataGenerated:requestData];
        }
    }
    else {
        NSLog(@"Error: Failed to create JSON representation - invalid args must have been given. %@", error ? error : @"No error given");
    }
    return requestData;
}

- (BOOL)performMethod:(NSString *)method args:(id)args requestId:(NSInteger)requestId
{
    // Check that we are capable of performing requests and handling responses.
    if (! [self sanityCheck]) {
        if (self.completionHandlerBlock) {
            JSONRPCError *error = [JSONRPCError new];
            error.errorCode = 0;
            error.errorMessage = @"Error performing sanity check on JSON RPC client.";
            self.completionHandlerBlock(requestId, NO, error);
        }
        return NO;
    }
    
    // Create the JSONRPC request data.
    NSData *requestData = [self buildRequest:method args:args requestId:requestId];
    if (requestData) {
        // Send the request.
        NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:self.serverURL];
        urlRequest.HTTPMethod = @"POST";
        urlRequest.HTTPBody = requestData;
        [urlRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        if (self.customHttpHeaders) {
            for (JSONRPCCustomHTTPHeader *header in self.customHttpHeaders) {
                [urlRequest setValue:header.value forHTTPHeaderField:header.key];
            }
        }
        
        JSONRPCRequestHelper *requestHelper;
        if (self.delegate) {
            requestHelper = [JSONRPCRequestHelper helperWithDelegate:self.delegate client:self];
        }
        else {
            requestHelper = [JSONRPCRequestHelper helperWithBlock:self.completionHandlerBlock client:self];
        }
        
        NSURLConnection *connection = [NSURLConnection connectionWithRequest:urlRequest delegate:requestHelper];
        if (connection) {
            [self.myRequests addObject:connection];
            [_outstandingNSURLConnections addObject:connection];
            [connection start];
            return YES;
        }
    }
    return NO;
}


#pragma mark - Multicall

- (NSMutableArray*)dispatchQueue
{
    if (_dispatchQueue == nil) {
        _dispatchQueue = [NSMutableArray array];
    }
    return _dispatchQueue;
}

- (BOOL)addMethodToQueue:(NSString *)method args:(id)args requestId:(NSInteger)requestId
{
    // Check that we are capable of performing requests and handling responses.
    if (! [self sanityCheck]) {
        return NO;
    }
    
    // Create the JSONRPC request data.
    NSData *requestData = [self buildRequest:method args:args requestId:requestId];
    if (requestData) {
        // Add the request to the queue.
        [self.dispatchQueue addObject:requestData];
    }
    return NO;
}

- (NSInteger)dispatch
{
    NSInteger queueSize = [self.dispatchQueue count];
    if (queueSize > 0) {
        // Build the request body.
        NSMutableData *requestData = [NSMutableData dataWithBytes:"[" length:1];
        for (NSData *request in self.dispatchQueue) {
            [requestData appendData:request];
            [requestData appendData:[@"," dataUsingEncoding:NSUTF8StringEncoding]];
        }
        [requestData replaceBytesInRange:NSMakeRange([requestData length] - 1, 1) withBytes:"]"];
                
        // Send the request.
        NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:self.serverURL];
        urlRequest.HTTPMethod = @"POST";
        urlRequest.HTTPBody = requestData;
        [urlRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        if (self.customHttpHeaders) {
            for (JSONRPCCustomHTTPHeader *header in self.customHttpHeaders) {
                [urlRequest setValue:header.value forHTTPHeaderField:header.key];
            }
        }
        NSURLConnection *connection = [NSURLConnection connectionWithRequest:urlRequest delegate:[JSONRPCRequestHelper helperWithDelegate:self.delegate client:self]];
        if (connection == nil) {
            return 0;
        }
        [self.myRequests addObject:connection];
        [_outstandingNSURLConnections addObject:connection];
        [connection start];
        
        [self.dispatchQueue removeAllObjects];
    }
    return queueSize;
}

-(NSArray*)getRequests
{
    return [NSArray arrayWithArray:self.myRequests];
}

@end
